# ---------------01 将 VGG16 卷积基实例化---------------
from keras.applications import VGG16

conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3))
# weights: None 代表随机初始化， 'imagenet' 代表加载在 ImageNet 上预训练的权值。
# include_top: 是否包括顶层的全连接层。
# input_shape: 可选，输入尺寸元组，仅当 include_top=False 时有效，否则输入形状必须是 (244, 244, 3)（对于 channels_last 数据格式），或者 (3, 244, 244)（对于 channels_first 数据格式）。它必须拥有 3 个输入通道，且宽高必须不小于 32。例如 (200, 200, 3) 是一个合法的输入尺寸。
# conv_base.summary()
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #
# =================================================================
# input_1 (InputLayer)         (None, 150, 150, 3)       0
# _________________________________________________________________
# block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792
# _________________________________________________________________
# block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928
# _________________________________________________________________
# block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0
# _________________________________________________________________
# block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856
# _________________________________________________________________
# block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584
# _________________________________________________________________
# block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0
# _________________________________________________________________
# block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168
# _________________________________________________________________
# block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080
# _________________________________________________________________
# block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080
# _________________________________________________________________
# block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0
# _________________________________________________________________
# block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160
# _________________________________________________________________
# block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808
# _________________________________________________________________
# block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808
# _________________________________________________________________
# block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0
# _________________________________________________________________
# block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808
# _________________________________________________________________
# block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808
# _________________________________________________________________
# block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808
# _________________________________________________________________
# block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0
# =================================================================
# Total params: 14,714,688
# Trainable params: 14,714,688
# Non-trainable params: 0
# _________________________________________________________________


# 最后的特征图形状为 (4, 4, 512)。我们将在这个特征上添加一个密集连接分类器。
# 接下来，下一步有两种方法可供选择。
# 1、在你的数据集上运行卷积基，将输出保存成硬盘中的 Numpy 数组，然后用这个数据作
# 为输入，输入到独立的密集连接分类器中（与本书第一部分介绍的分类器类似）。这种
# 方法速度快，计算代价低，因为对于每个输入图像只需运行一次卷积基，而卷积基是目
# 前流程中计算代价最高的。但出于同样的原因，这种方法不允许你使用数据增强。
# 2、在顶部添加 Dense 层来扩展已有模型（即 conv_base），并在输入数据上端到端地运行
# 整个模型。这样你可以使用数据增强，因为每个输入图像进入模型时都会经过卷积基。
# 但出于同样的原因，这种方法的计算代价比第一种要高很多。

# ---------------不使用数据增强的快速特征提取---------------


import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator

base_dir = '/home/python-test/py36-keras-demo01/cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')
datagen = ImageDataGenerator(rescale=1. / 255)
batch_size = 20


# 使用预训练的卷积基提取特征
def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 4, 4, 512))
    labels = np.zeros(shape=(sample_count))
    generator = datagen.flow_from_directory(
        directory,
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode='binary'
    )
    i = 0
    for inputs_batch, labels_batch in generator:
        features_batch = conv_base.predict(inputs_batch)
        features[i * batch_size:(i + 1) * batch_size] = features_batch
        labels[i * batch_size:(i + 1) * batch_size] = labels_batch
        i += 1
        if i * batch_size >= sample_count:
            break  # 注意，这些生成器在循环中不断生成数据，所以你必须在读取完所有图像后终止循环
    return features, labels


train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

# 目前，提取的特征形状为 (samples, 4, 4, 512)。我们要将其输入到密集连接分类器中，所以首先必须将其形状展平为 (samples, 8192)。
train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))

# ---------------定义并训练密集连接分类器---------------
# 现在你可以定义你的密集连接分类器（注意要使用 dropout 正则化），并在刚刚保存的数据和标签上训练这个分类器。
from keras import models
from keras import layers
from keras import optimizers

model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))

# ---------------编译  密集连接分类器模型---------------
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
              loss='binary_crossentropy',
              metrics=['acc'])

# ---------------训练  密集连接分类器模型---------------

from keras.callbacks import ModelCheckpoint

filepath = "dog_vs_cat_vgg16_no_data_enhancement-{epoch:02d}-{val_acc:.4f}.hdf5"
# ModelCheckpoint保存模型
checkpoint = ModelCheckpoint(filepath, monitor='val_acc', verbose=1, save_best_only=True,
                             mode='max')
callbacks_list = [checkpoint]

history = model.fit(train_features, train_labels,
                    epochs=30,
                    batch_size=20,
                    validation_data=(validation_features, validation_labels),
                    callbacks=callbacks_list)

# ---------------绘制结果---------------
import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show(block=False)
plt.show()
